/**
 * DonReY's Template System - JavaScript version
 * Added to the String object, so any string can be used as a template, and combined with the "data".
 * 
 * The syntax is nearly identical to DRY\Render\Transform_Drytemplate, however instead of PHP arrays or objects, the data is a javascript object. 
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Modules/render.drytemplate.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Render
 *
 * @param data The object data that is merged with the template.
 * @return string
 */
String.prototype.drytemplate = function(rootdata) {
	
	var
	measure_String = function(string, p)
	{
		var s = p;
		len = string.length;
		do {
			p++;
			// end of string
			if(string.charAt(p) == '"') {
				break;
			}
			// check for escaped quotes inside the string ( \" ), and skip them, also check for backslash itself ( \\ )
			if(string.charAt(p) == '\\') {
				if(string.charAt(p+1) == '"' || string.charAt(p+1) == '\\') {
					p++;
				}
			}
			if(p >= len-1)
				throw "Unterminated string";
		} while(1);

		return p;
	},
	
	/**
	 * Scans the interior of a placeholder, which follows various syntax rules
	 *
	 * @param string placeholder
	 * @param array modifiers
	 */
	parse_Placeholder = function(placeholder, modifiers)
	{
		if(placeholder.length == 0)
			return undefined; // nothing to parse!

		var ph = {}, kwd, explode_dc = function(string, limit) {
			if (!limit) {
		        return string.toString().split(':');
		    }
			var splitted = string.split(':'),
				partA = splitted.splice(0, limit - 1),
				partB = splitted.join(':');
		    partA.push(partB);
		    return partA;
		};

		// check if first character is special ( !, @, $ or # )
		switch(placeholder.charAt(0)) {
			// comment, the entire content of the placeholder is ignored, including any (useless) modifiers
			case '!':
				return undefined;
			// literal section, doesn't get parsed, but modifier processing does occur
			case '@':
				if(modifiers) {
					ph.mode = 'L';
					ph.str = placeholder.substr(1);
					break;
				} else
					return placeholder.substr(1);
			// $ represents the data itself - used when you don't want to search inside the data like for variables, but the data itself is what must appear
			// usually in conjunction with #use or #repeat
			case '$':
				ph.mode = 'SELF';
				break;
			// keyword, special meaning for each
			case '#':

				kwd = explode_dc(placeholder, 3);
				ph.mode = 'KW';
				ph.keyword = kwd[0].substr(1).toUpperCase();

				// basic or dynamic variable
				var trec1 = [], trec2 = [];
				parse_Pattern(kwd[1], trec1);
				if(trec1.length == 1 && typeof trec1[0] == 'string') {
					// basic variable (no more curly brackets inside)
					ph.variable = trec1[0];
				} else {
					// dynamic variable
					ph.variable = trec1;
				}

				switch(ph.keyword) {
					// the 3rd part doesn't exist
					case 'BREAK':
						break;
					// the 3rd part is raw data, don't go into it ( similar to {@...}
					case 'EXPRESSION':
						ph.rawdata = kwd[2];
						break;
					// the 3rd part is a pattern
					case 'USE':
					case 'ROOT':
					case 'IF':
					case 'IFNOT':
					case 'REPEAT':
						parse_Pattern(kwd[2], trec2);
						if(trec2.length == 1 && typeof trec2[0] == 'string') {
							ph.pattern = trec2[0];
						} else {
							ph.pattern = trec2;
						}
						break;
				}
				break;
			// basic or dynamic variable name
			default:
				ph.mode = 'VAR';

				// split into var and default
				var vd = explode_dc(placeholder, 2), def = [], trec = [];

				// parse the interior (for dynamic var names)
				parse_Pattern(vd[0], trec);
				if(trec.length == 1 && typeof trec[0] == 'string') {
					// basic variable (no more curly brackets inside)
					ph.variable = trec[0];
				} else {
					// dynamic variable
					ph.variable = trec;
				}

				// check if default exists
				if(vd[1]) {
					parse_Pattern(vd[1], def);
					if(def.length == 1 && typeof def[0] == 'string') {
						ph.def = def[0];
					} else {
						ph.def = def;
					}
				}
		}
		// modifiers
		if(modifiers)
			ph.mods = modifiers;
		return ph;
	},
	
	/**
	 * Scans the interior of a modifiers section, which follows various syntax rules
	 * Returns the "modifiers" object which can be used by parse_Placeholder
	 *
	 * @param string $modifiers
	 */
	parse_Modifiers = function(modifiers)
	{
		if(modifiers.length == 0)
			return undefined; // nothing to parse!
		
		return undefined; // TODO, later.
	},
	
	
	/**
	 * Parses the template (this) into it's components ( identifies all placeholders and other special elements, saves fragments )
	 * Calls iteself recursively for patterns inside placeholders.
	 *
	 * @param array record Analysis recording, an array of elements
	 */
	parse_Pattern = function(pattern, record)
	{
		// Initialization: pattern position, length, where scanning has begin, test-depth inside curly brackets
		var p = 0, len = pattern.length, check_start = 0, depth = 0, begin, end, modifier, mod_start;
		if(len == 0)
			return; // no template !
		
		// identify the start and stop of a {placeholder} . It might have other {}'s inside it
		do {
			// If we are inside a placeholder ( $depth > 0 ), {}'s might exist inside a string, so if a quote is detected, find the end of the string first
			if(depth > 0) {
				if(pattern.charAt(p) == '"') {
					p = measure_String(pattern, p);
				}
			}
			// not inside brackets, not in string, find the matching end bracket
			if(pattern.charAt(p) == '{') {
				if(depth == 0) {
					begin = p + 1;
				}
				depth++;
			}
			// }'s are ignored if a placeholder hasn't been started
			if(depth > 0 && pattern.charAt(p) == '}') {
				depth--;
				// bingo, here it is, analyse the placeholder
				if(depth == 0) {
					end = p;

					// first, record the plain-text fragment before the placeholder (only if it's length is > 0 )
					if(begin - check_start-1 > 0)
						record.push(pattern.substr(check_start, begin - check_start-1));
					// move past the } character
					p++;

					// after placeholders, a modifier section can appear ( which is delimited by two ~ ~ ), check if there is such item
					modifier = undefined;
					if(p < len-1 && pattern.charAt(p) == '~') { // yes !
						mod_start = p + 1;
						do {
							p++;
							// check for strings inside modifiers ( they might contain ~ character, so it must be ignored )
							if(pattern.charAt(p) == '"') {
								p = measure_String(pattern, p);
							}
							if(pattern.charAt(p) == '~') { // found the end
								modifier = parse_Modifiers(pattern.substr(mod_start, p - mod_start));
								p++;
								break;
							}
							if(p == len-1)
								throw "Unterminated modifier section";
						} while(1);
					}
					// then, the placeholder ( which is an object )
					var ph = parse_Placeholder(pattern.substr(begin, end - begin), modifier);
					if(ph)
						record.push(ph);
					// continue from after the placeholder
					check_start = p;
					// p has already been advanced after the placeholder
					continue;
				}
			}
			p++;
		} while(p < len);
		
		// oops, trouble!
		if(depth > 0) {
			throw "Unterminated placeholder !";
		}
		// when we're done, record the last part of the string
		if(check_start < len)
			record.push(pattern.substr(check_start));
	},

	/**
	 * Copies an object ( by default JS uses references, not copies ! )
	 */
	clone = function(obj)
	{
	    if (null == obj || "object" != typeof obj) return obj;
	    var copy = obj.constructor();
	    for (var attr in obj) {
	        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
	    }
	    return copy;
	},
	
	/**
	 * Scans data for a value using the dot.syntax format
	 * It searches in array or objects, dynamically
	 *
	 * @param string key Dot.syntax.formatted.data.key
	 * @param mixed usedata Data to be searched
	 * @param string def Default text for when data is not found (or unsearchable)
	 */
	scan_Data = function(key, usedata, def)
	{
		if(key.length == 0) // no key, return data as is
			return usedata;
		
		if(!def)
			def = '';
		
		var data, p = key.split('.'), s;

		// ^ caret in the beginning of a key name forces the data to be searched in $rootdata instead of the current location.
		// Useful when location has been overridden by #repeat or #use and we want to access outside it.
		// Ignores the passed $data
		if(key.charAt(0) == '^') {
			key = key.substr(1);
			data = clone(rootdata);
		} else
			data = clone(usedata);

		if(data.length == 0)
			return undefined; // ... nothing to search for, nothing to return

		// search in object for the proper key (recursive)
		try {
			for(s in p) {
				data = data[p[s]];
			}
		} catch(e) {
			if (typeof def === 'undefined') {
				data = '';
			} else {
				data = def;
			}
		}
        return data;
	},
	
	/**
	 * Little helper function for all keywords that need a "condition"
	 *
	 * @param string $varname
	 * @param mixed $data
	 * @return bool
	 */
	check_Condition = function(varname, data)
	{
		var condition = scan_Data(varname, data);
		if(typeof condition == 'array' || typeof condition == 'object' || typeof condition == 'string')
			condition = condition.length > 0;
		else
			condition = condition == true;
		return condition;
	},
	
	/**
	 * Processes parsed template (which is now an array of fragments and objects) and returns the final result
	 *
	 * @param array ptpl Parsed TemPLate
	 */
	process_Parsed = function(ptpl, data)
	{
		var dataref , s = '', varname, str, fragment;
		if(typeof ptpl == 'string') // ?
			s = ptpl;
		else

		for(var c=0; c<ptpl.length; c++) {
			fragment = ptpl[c];
			
			if(typeof fragment == 'string') // plain text, no processing, no replacing
				s+= fragment;
			else {
				// if -> var exist, assamble it
				if(fragment.variable)
					if(typeof fragment.variable == 'string') {
						// simple var (dot syntax, use the data scanner to locate it
						varname = fragment.variable;
					} else {
						// dynamic var (has subpatterns)
						varname = process_Parsed(fragment.variable, data);
					}
				// else, reset it (clear last iteration)
				else
					varname = '';

				// here comes the fun part, it depends on what kind of placeholder
				switch(fragment.mode) {

					// Literal
					case 'L':
						str = fragment.str.toString();

//						if(fragment.mods)
//							s+= modify_String(str, fragment.mods);
//						else
							s+= str;
						break;

					// data itself
					case 'SELF':
						s+= data.toString();

						break;
					// search variable in data
					case 'VAR':

						var def = '', patternmode, varcontent, pa;
						// grab or process default
						if(fragment.def) {
							if(typeof fragment.def == 'string')
								def = fragment.def;
							else
								def = process_Parsed(fragment.def, data);
						}
						
						// ~ tilde in the beginning of a variable name forces the content of a varible to behave like a pattern ( and get run through the parser )
						// Note: When used along the ^ modifier, they must appear in the order: ~^ ( ^~ won't work )
						if(varname.charAt(0) == '~') {
							varname = varname.substr(1);
							patternmode = true;
						} else
							patternmode = false;

						varcontent = scan_Data(varname, data, def).toString(); // convert found data to (string)

						if(patternmode) { // not that this cannot be pre-parsed, it's like PHP's eval()
							pa = [];
							parse_Pattern(varcontent, pa);
							varcontent = process_Parsed(pa, data);
						}

//						if(fragment.mods)
//							s+= modify_String(varcontent, fragment.mods);
//						else
							s+= varcontent;
						break;
					// Keyword
					case 'KW':
						
						var usedata;
						
						switch(fragment.keyword) {

							// {#use:var:pattern}
							case 'USE':
								usedata = scan_Data(varname, data);
								str = process_Parsed(fragment.pattern, usedata); // use data as is, this keyword needs it.

//								if(fragment.mods)
//									s+= modify_String(str, fragment.mods);
//								else
									s+= str;
								break;

							case 'ROOT':
								usedata = scan_Data(varname, rootdata);
								str = process_Parsed(fragment.pattern, usedata); // use data as is, this keyword needs it.

//								if(fragment.mods)
//									s+= modify_String(str, fragment.mods);
//								else
									s+= str;
								break;

							// {#use:condition:pattern}
							case 'IF':
								if(check_Condition(varname, data)) {
									str = process_Parsed(fragment.pattern, data);

//									if(fragment.mods)
//										s+= modify_String(str, fragment.mods);
//									else
										s+= str;
								}
								break;

							// {#ifnot:condition:pattern}
							case 'IFNOT':
								// condition variable analysed, now test it
								if(!check_Condition(varname, data)) {
									str = process_Parsed(fragment.pattern, data);

//									if(fragment.mods)
//										s+= modify_String(str, fragment.mods);
//									else
										s+= str;
								}
								break;

							// {#repeat:iterative-var:pattern}
							case 'REPEAT':
								usedata = scan_Data(varname, data);
								if(!usedata)
									break;
								
								if(typeof usedata == 'array' || typeof usedata == 'object') {
									// data is iterable (or it should be)
									for(var idx in usedata) {
										var processed_element = process_Parsed(fragment.pattern, usedata[idx]),
											breakpoint = processed_element.indexOf('%%BREAK%%');

										if(breakpoint!==-1) {
											str = processed_element.substr(0, breakpoint);
											break;
										} else
											str = processed_element;
										// modification is applied to each iteration individually, not as a whole (for example, cutting affects the iteration, not the full string resulting from repeat)
//										if(fragment.mods)
//											s+= modify_String(str, fragment.mods);
//										else
											s+= str;
									}
								}
								break;

							// {#break:condition}
							case 'BREAK':
								// if condition is true, returns a special notification to the above level (which should only be a #repeat keyword!)
								if(check_Condition(varname, data))
									s+= '%%BREAK%%';
								break;

						}
						break;
				}

			}
		}
		return s;
	}
	;
	
	var r = [];
	parse_Pattern(this, r);
	return process_Parsed(r, rootdata);
};